﻿using System;
using System.Linq;
using System.Threading.Tasks;
using BookSleeve;
using Newtonsoft.Json;
using RedisDemos.Extensions;

namespace RedisDemos.Scenarios
{
    public class Timelines : Scenario
    {
        protected override async Task Perform(RedisConnection conn)
        {
            var username = "tank-1";

            await BootstrapUserProfile(conn, username);

            await NewPost(conn, username, new ActivityModel { Content = String.Format("{0} ha stretto amicizia con nighes e altre 9 persone.", username), At = DateTime.Now.AddHours(-1) });
            await NewPost(conn, username, new ActivityModel { Content = String.Format("{0} ha totalizzato 1000 punti a Prato fiorito piazzandosi al 10° posto.", username), At = DateTime.Now.AddMinutes(-30) });
            await NewPost(conn, username, new ActivityModel { Content = String.Format("A {0} piace il gioco Prato fiorito.", username), At = DateTime.Now.AddMinutes(-2) });
            await NewPost(conn, username, new ActivityModel { Content = String.Format("In questa giornata di pioggia non riesco a smettere di giocare.", username), At = DateTime.Now.AddMinutes(-1) });
            await NewPost(conn, username, new ActivityModel { Content = String.Format("naighes ha commentato il post di {0}: ma vai a lavorare!!!", username), At = DateTime.Now });

            var profile = await ShowPublicProfile(conn, username);
            Print(profile);
        }

        private void Print(PublicProfileModel profile)
        {
            "{0} ({1})".LogInfo(profile.UserName, profile.Twitter);
            "{0} {1}".LogInfo(profile.FirstName, profile.LastName);
            "Activity:".LogInfo();
            profile.Activity.ForEach(a => "{0}: {1}".LogInfo(a.At, a.Content));
        }

        private async Task<PublicProfileModel> ShowPublicProfile(RedisConnection conn, String username)
        {
            // GET key
            var userId = await conn.Strings.Get(Db, String.Format("username:{0}", username));
            
            // parallel invoke commands
            // HGETALL key 
            // LRANGE key start stop 
            var getFieldsTask = conn.Hashes.GetAll(Db, String.Format("user:{0}", userId.AsString()));
            var getLastTwentyActivityTask = conn.Lists.RangeString(Db, String.Format("activity:{0}", username), 0, 19);

            // await completion
            var profile = (await getFieldsTask).AsObject<PublicProfileModel>();
            profile.Activity = (await getLastTwentyActivityTask)
                                .Select(data => JsonConvert.DeserializeObject<ActivityModel>(data))
                                .ToList();
            
            return profile;
        }

        private async Task BootstrapUserProfile(RedisConnection conn, String username)
        {
            await RegisterUser(conn, new UserModel
            {
                UserName = username,
                Password = "not safe pwd",
                Email = "some.fake@email.com",
                JoinedOn = DateTime.Today.AddDays(-1),
            });

            var profile = await FetchProfile(conn, username);
            profile.FirstName = "Matteo";
            profile.LastName = "Baglini";
            profile.SubscribeNewsletter = true;
            profile.Twitter = "@DotNetToscana";
            await SaveProfile(conn, profile);
        }

        private async Task NewPost(RedisConnection conn, String username, ActivityModel model)
        {
            var data = JsonConvert.SerializeObject(model);

            // LPUSH key value [value ...] 
            await conn.Lists.AddFirst(Db, String.Format("activity:{0}", username), data);

            // prevent indefinite growth
            // LTRIM key start stop 
            await conn.Lists.Trim(Db, String.Format("activity:{0}", username), 0, 99);
        }

        private async Task RegisterUser(RedisConnection conn, UserModel model)
        {
            // SADD key member [member ...] 
            var wasAdded = await conn.Sets.Add(Db, "username:all", model.UserName);
            if (wasAdded == false)
                throw new InvalidOperationException("User name already exists.");

            // INCR key 
            var newId = await conn.Strings.Increment(Db, "users:nextid");

            // SET key value
            // HMSET key field value [field value ...] 
            await Task.WhenAll(conn.Strings.Set(Db, String.Format("username:{0}", model.UserName), newId),
                               conn.Hashes.Set(Db, String.Format("user:{0}", newId), model.AsHash()));
        }

        private async Task<ProfileModel> FetchProfile(RedisConnection conn, String username)
        {
            if (String.IsNullOrWhiteSpace(username))
                throw new InvalidOperationException("The user name provided is invalid.");

            // GET key
            var userId = await conn.Strings.Get(Db, String.Format("username:{0}", username));
            if (userId == null)
                throw new InvalidOperationException("The user name provided is unknown.");

            // HGETALL key 
            var fields = await conn.Hashes.GetAll(Db, String.Format("user:{0}", userId.AsString()));
            return fields.AsObject<ProfileModel>();
        }

        private async Task SaveProfile(RedisConnection conn, ProfileModel profile)
        {
            // GET key
            var userId = await conn.Strings.Get(Db, String.Format("username:{0}", profile.UserName));
            if (userId == null)
                throw new InvalidOperationException("The user name provided is unknown.");

            // HMSET key field value [field value ...] 
            await conn.Hashes.Set(Db, String.Format("user:{0}", userId.AsString()), profile.AsHash());
        }
    }
}
